package com.ian.config;

import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.jwt.proc.JWTClaimsSetAwareJWSKeySelector;
import com.nimbusds.jwt.proc.JWTProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.*;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.HeaderBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;

import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.security.oauth2.jose.jws.SignatureAlgorithm.*;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;

/**
 * @author Witt
 * @version 1.0.0
 * @date 2022/5/19
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuerUri;

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;

    @Value("${key.public.location}")
    private RSAPublicKey rsaPublicKey;

    @Value("${key.secret.location}")
    private SecretKey secretKey;

    @Autowired
    private AuthenticationManagerResolver tokenAuthenticationManagerResolver;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*
         Bearer Tokens: 1 Bearer Token Resolution: 2 Reading the Bearer Token from a Form Parameter
         Or, you may wish to read the token from a form parameter, which you can do by configuring the DefaultBearerTokenResolver, as you can see below:
         Example 2. Form Parameter Bearer Token
         */
        DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
        bearerTokenResolver.setAllowFormEncodedBodyParameter(true);

        /*
        JWT: Example 1. Default JWT Configuration
        http
                .authorizeHttpRequests(authorize -> authorize
                    .anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
         */
        // JWT: Example 2. Custom JWT Configuration
        http
                .authorizeHttpRequests(authorize -> authorize
                        /*
                         JWT: Example 8. Authorization Configuration
                         A JWT that is issued from an OAuth 2.0 Authorization Server will typically either have a scope or scp attribute, indicating the scopes (or authorities) it’s been granted,
                         for example:  { …, "scope" : "messages:read contacts"}
                        When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string "SCOPE_".
                        This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
                         */
                        .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
                        .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages:read")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                // 下面的 JwtDecoder bean的配置，can be overridden using jwkSetUri() or replaced using decoder().
                                //      More powerful than jwkSetUri() is decoder(), which will completely replace any Boot auto configuration of JwtDecoder
                                .decoder(myCustomDecoder())
                                //      Using jwkSetUri() takes precedence over any configuration property
                                .jwkSetUri("https://idp.example.com/.well-known/jwks.json")
                                .jwtAuthenticationConverter(myConverter()))
                        /*
                         Multitenancy: Supporting both JWT and Opaque Token: And then specify this AuthenticationManagerResolver in the DSL:
                         Example 1. Authentication Manager Resolver
                         */
                        // .authenticationManagerResolver(tokenAuthenticationManagerResolver)

                        /*
                         Multitenancy: Resolving the Tenant By Claim: Example 2. Multitenancy Tenant by JWT Claim
                         This is nice because the issuer endpoints are loaded lazily. In fact, the corresponding JwtAuthenticationProvider is instantiated only when the first request with the corresponding issuer is sent. This allows for an application startup that is independent from those authorization servers being up and available.
                         */
                        .authenticationManagerResolver(jwtIssuerAuthenticationManagerResolver)
                        /*
                         Bearer Tokens: 1 Bearer Token Resolution: 2 Reading the Bearer Token from a Form Parameter
                         Or, you may wish to read the token from a form parameter, which you can do by configuring the DefaultBearerTokenResolver, as you can see below:
                         Example 2. Form Parameter Bearer Token
                         */
                        .bearerTokenResolver(bearerTokenResolver)

                )
                                // JWT: For more flexibility, the DSL supports entirely replacing the converter with any class that implements Converter<Jwt, AbstractAuthenticationToken>:
//                                .jwtAuthenticationConverter(new CustomAuthenticationConverter())

//                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
        ;
    }

    /**
     * 测试方法，仅用于防止编译错误
     * @return
     */
    private Converter<Jwt,? extends AbstractAuthenticationToken> myConverter() {
        return jwt -> null;
    }

    /**
     * JWT: Example 3. JWT Decoder 1
     * creates is a JwtDecoder, which decodes String tokens into validated instances of Jwt:
     *
     * @return
     */
    @Bean
    public JwtDecoder jwtDecoder() {
        /*
        Calling JwtDecoders#fromIssuerLocation is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri.
        If the application doesn’t expose a JwtDecoder bean, then Spring Boot will expose this default one.
        这个最终返回的实现类也是 NimbusJwtDecoder
        */
        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromIssuerLocation(issuerUri);

        /*
        Customizing Timestamp Validation
            JWT’s typically have a window of validity, with the start of the window indicated in the nbf claim and the end indicated in the exp claim.
            However, every server can experience clock drift, which can cause tokens to appear expired to one server, but not to another. This can cause some implementation heartburn as the number of collaborating servers increases in a distributed system.
            Resource Server uses JwtTimestampValidator to verify a token’s validity window, and it can be configured with a clockSkew to alleviate the above problem:
         */
        OAuth2TokenValidator<Jwt> withClockSkew = new DelegatingOAuth2TokenValidator(
                // By default, Resource Server configures a clock skew of 60 seconds.
                new JwtTimestampValidator(Duration.ofSeconds(60)),
                new JwtIssuerValidator(issuerUri),
                audienceValidator()
        );

        jwtDecoder.setJwtValidator(withClockSkew);
        return jwtDecoder;
    }

    /**
     * JWT: Example 3. JWT Decoder 2
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    JwtDecoder myCustomDecoder() {
        return NimbusJwtDecoder
                .withJwkSetUri(jwkSetUri)
                // JWT: Configuring Trusted Algorithms - 2 Using a Builder
                .jwsAlgorithm(RS512)
                // Calling jwsAlgorithm more than once will configure NimbusJwtDecoder to trust more than one algorithm, like so:
                .jwsAlgorithm(ES512)
                // Or, you can call jwsAlgorithms:
                .jwsAlgorithms(algorithms -> {
                    algorithms.add(RS512);
                    algorithms.add(ES512);
                })
                .build();
    }

    // JWT: Configuring Trusted Algorithms - 3 From JWK Set response
    @Bean
    @ConditionalOnMissingBean
    public JwtDecoder jwtDecoder2() throws MalformedURLException, KeySourceException {
        // makes a request to the JWK Set endpoint
        JWSKeySelector<SecurityContext> jwsKeySelector = JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(jwkSetUri));
        DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
        jwtProcessor.setJWSKeySelector(jwsKeySelector);

        return new NimbusJwtDecoder(jwtProcessor);
    }

    /*
     JWT: Trusting a Single Asymmetric Key - 1 Via Spring Boot
             Or, to allow for a more sophisticated lookup, you can post-process the RsaKeyConversionServicePostProcessor,
             then, Specify your key’s location in application.yml
             说实话，没看懂
    @Bean
    BeanFactoryPostProcessor conversionServiceCustomizer() {
        return beanFactory -> {beanFactory.getBean(RsaKeyConversionServicePostProcessor.class)
                .setResourceLoader(new CustomResourceLoader());
        };
    }
     */

    @Bean
    @ConditionalOnMissingBean
    JwtDecoder myCustomDecoder2() {
        return NimbusJwtDecoder
                // JWT: Trusting a Single Asymmetric Key - 2 Using a Builder
                .withPublicKey(rsaPublicKey)
                .build();
    }

    /**
     * JWT: Trusting a Single Symmetric Key - Using a Builder
     * Using a single symmetric key is also simple. You can simply load in your SecretKey and use the appropriate NimbusJwtDecoder builder, like so:
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    JwtDecoder myCustomDecoder3() {
        return NimbusJwtDecoder
                .withSecretKey(secretKey)
                .build();
    }


    /**
     * JWT: Extracting Authorities Manually
     * However, there are a number of circumstances where this default is insufficient. For example, some authorization servers don’t use the scope attribute, but instead have their own custom attribute. Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.
     * To this end, Spring Security ships with JwtAuthenticationConverter, which is responsible for converting a Jwt into an Authentication. By default, Spring Security will wire the JwtAuthenticationProvider with a default instance of JwtAuthenticationConverter.
     * As part of configuring a JwtAuthenticationConverter, you can supply a subsidiary converter to go from Jwt to a Collection of granted authorities.
     * Let’s say that that your authorization server communicates authorities in a custom claim called authorities. In that case, you can configure the claim that JwtAuthenticationConverter should inspect, like so:
     *
     * JWT: Example 9. Authorities Claim Configuration
     *
     * @return
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        /*
        You can also configure the authority prefix to be different as well. Instead of prefixing each authority with SCOPE_, you can change it to ROLE_ like so:
        JWT: Example 10. Authorities Prefix Configuration
         */
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        // remove the prefix altogether
//        grantedAuthoritiesConverter.setAuthorityPrefix("");


        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

    /*
    JWT: For more flexibility, the DSL supports entirely replacing the converter with any class that implements Converter<Jwt, AbstractAuthenticationToken>:
    以下静态内部类，仅用于测试，只是保证编译时不报错，并不能实际使用。
     */

    static class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
        @Override
        public AbstractAuthenticationToken convert(Jwt jwt) {
            return new CustomAuthenticationToken(jwt);
        }

        private class CustomAuthenticationToken extends AbstractAuthenticationToken {
            public CustomAuthenticationToken(Jwt jwt) {
                super(Collections.emptyList());
            }

            @Override
            public Object getCredentials() {
                return null;
            }

            @Override
            public Object getPrincipal() {
                return null;
            }
        }
    }

    /**
     * JWT: Configuring a Custom Validator
     * Adding a check for the aud claim is simple with the OAuth2TokenValidator API:
     *
     */
    OAuth2TokenValidator<Jwt> audienceValidator() {
        return new JwtClaimValidator<List<String>>(AUD, aud -> aud.contains("messaging"));
    }

    /**
     * JWT: Configuring a Custom Validator
     * Or, for more control you can implement your own OAuth2TokenValidator
     */
    static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
        OAuth2Error error = new OAuth2Error("custom_code", "Custom error message", null);

        @Override
        public OAuth2TokenValidatorResult validate(Jwt jwt) {
            if (jwt.getAudience().contains("messaging")) {
                return OAuth2TokenValidatorResult.success();
            } else {
                return OAuth2TokenValidatorResult.failure(error);
            }
        }
    }
    // JWT: Configuring a Custom Validator
    // Then, to add into a resource server, it’s a matter of specifying the JwtDecoder instance:
    OAuth2TokenValidator<Jwt> audienceValidator2() {
        return new AudienceValidator();
    }

    // JWT: Configuring a Custom Validator
    // Then, to add into a resource server, it’s a matter of specifying the JwtDecoder instance:
    @Bean
    public JwtDecoder jwtDecoder3() {

        NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);

        OAuth2TokenValidator<Jwt> audienceValidator = audienceValidator2();
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<Jwt>(audienceValidator, withIssuer);

        jwtDecoder.setJwtValidator(withAudience);
        return jwtDecoder;
    }

    /**
     * JWT: Customizing the Conversion of a Single Claim
     */
    @Bean
    public JwtDecoder jwtDecoder4() {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();

        MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
                // This will keep all the defaults, except it will override the default claim converter for sub.
//                .withDefaults(Collections.singletonMap("sub", this::lookupUserIdBySub))
                // MappedJwtClaimSetConverter can also be used to add a custom claim, for example, to adapt to an existing system:
//                .withDefaults(Collections.singletonMap("custom", custom -> "customValue"))
                // And removing a claim is also simple, using the same API:
                .withDefaults(Collections.singletonMap("legacyclaim", legacy -> null))
        ;
        jwtDecoder.setClaimSetConverter(converter);
        return jwtDecoder;
    }
    // 该方法仅用于测试，保证编译时不报错
    private Object lookupUserIdBySub(Object o) {
        return "userid1";
    }

    /**
     * JWT: Renaming a Claim
     * In more sophisticated scenarios, like consulting multiple claims at once or renaming a claim,
     * Resource Server accepts any class that implements Converter<Map<String, Object>, Map<String,Object>>:
     *
     * 注：我觉得该类的名字取得不对，UsernameSubClaimAdapter 应该改为 UsernameSubClaimDecorator
     *      因为这其实使用了装饰者模式，而非适配器模式
     *
     */
    public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> {

        private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());

        @Override
        public Map<String, Object> convert(Map<String, Object> claims) {
            Map<String, Object> convertedClaims = this.delegate.convert(claims);

            String username = (String) convertedClaims.get("user_name");
            convertedClaims.put("sub", username);

            return convertedClaims; // And then, the instance can be supplied like normal（参考下面的 jwtDecoder5()）
        }
    }

    /**
     * JWT: Renaming a Claim
     * And then, the instance can be supplied like normal:
     */
    @Bean
    public JwtDecoder jwtDecoder5() {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
        jwtDecoder.setClaimSetConverter(new UsernameSubClaimAdapter());
        return jwtDecoder;
    }

    /**
     * JWT: Configuring Timeouts
     * By default, Resource Server uses connection and socket timeouts of 30 seconds each for coordinating with the authorization server.
     * This may be too short in some scenarios. Further, it doesn’t take into account more sophisticated patterns like back-off and discovery.
     * To adjust the way in which Resource Server connects to the authorization server, NimbusJwtDecoder accepts an instance of RestOperations:
     */
    @Bean
    public JwtDecoder jwtDecoder6(RestTemplateBuilder builder) {
        RestOperations rest = builder
                .setConnectTimeout(Duration.ofSeconds(60))
                .setReadTimeout(Duration.ofSeconds(60))
                .build();

        return NimbusJwtDecoder
                .withJwkSetUri(jwkSetUri)
                .restOperations(rest)
                .build();
    }

    /**
     * Also by default, Resource Server caches in-memory the authorization server’s JWK set for 5 minutes, which you may want to adjust. Further, it doesn’t take into account more sophisticated caching patterns like eviction or using a shared cache.
     * To adjust the way in which Resource Server caches the JWK set, NimbusJwtDecoder accepts an instance of Cache:
     *
     * Note1: When given a Cache, Resource Server will use the JWK Set Uri as the key and the JWK Set JSON as the value.
     * Note2: Spring isn’t a cache provider, so you’ll need to make sure to include the appropriate dependencies, like spring-boot-starter-cache and your favorite caching provider.
     * Note3: Whether it’s socket or cache timeouts, you may instead want to work with Nimbus directly. To do so, remember that NimbusJwtDecoder ships with a constructor that takes Nimbus’s JWTProcessor.
     *
     * @param cacheManager
     * @return
     */
    @Bean
    public JwtDecoder jwtDecoder6(CacheManager cacheManager) {

        return NimbusJwtDecoder
                .withJwkSetUri(jwkSetUri)
                .cache(cacheManager.getCache("jwks"))
                .build();
    }

    /**
     * Multitenancy: Supporting both JWT and Opaque Token
     * In some cases, you may have a need to access both kinds of tokens. For example, you may support more than one tenant where one tenant issues JWTs and the other issues opaque tokens.
     * If this decision must be made at request-time, then you can use an AuthenticationManagerResolver to achieve it, like so:
     */
    @Bean
    AuthenticationManagerResolver<HttpServletRequest> tokenAuthenticationManagerResolver
        (JwtDecoder jwtDecoder, OpaqueTokenIntrospector opaqueTokenIntrospector) {
        AuthenticationManager jwt = new ProviderManager(new JwtAuthenticationProvider(jwtDecoder));
        AuthenticationManager opaqueToken = new ProviderManager(new OpaqueTokenAuthenticationProvider(opaqueTokenIntrospector));
        // The implementation of useJwt(HttpServletRequest) will likely depend on custom request material like the path.
        return request -> useJwt(request) ? jwt : opaqueToken;
    }

    // The implementation of useJwt(HttpServletRequest) will likely depend on custom request material like the path.
    private boolean useJwt(HttpServletRequest request) {

        String requestURI = request.getRequestURI();
        if (StringUtils.hasText(requestURI) && requestURI.contains("jwt")) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Multitenancy: Resolving the Tenant By Claim
     * One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, this can be done with the JwtIssuerAuthenticationManagerResolver, like so:
     * Example 2. Multitenancy Tenant by JWT Claim
     */
    private JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver
            ("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");

    /**
     * Multitenancy: Resolving the Tenant By Claim: Dynamic Tenants
     * Of course, you may not want to restart the application each time a new tenant is added.
     * In this case, you can configure the JwtIssuerAuthenticationManagerResolver with a repository of AuthenticationManager instances,
     * which you can edit at runtime, like so:
     */
    private Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();

    private void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
        JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider
                (JwtDecoders.fromIssuerLocation(issuer));
        authenticationManagers.put(issuer, authenticationProvider::authenticate);
    }

    JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver2 =
            new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get);

    /*
    最后，将 jwtIssuerAuthenticationManagerResolver2 设置到 Security配置中
    http
    .authorizeHttpRequests(authorize -> authorize
        .anyRequest().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(jwtIssuerAuthenticationManagerResolver2)
    );

    In this case, you construct JwtIssuerAuthenticationManagerResolver with a strategy for obtaining the AuthenticationManager given the issuer.
    This approach allows us to add and remove elements from the repository (shown as a Map in the snippet) at runtime.
    有空复习下策略模式，然后看这里是怎么使用策略模式的？
    答：首先，通过方法引用（authenticationManagers::get）生成 AuthenticationManagerResolver<String> 对象，
            该对象传入 JwtIssuerAuthenticationManagerResolver 的构造器，在该构造器内部，其又传入 ResolvingAuthenticationManager 的构造器，构造了一个
            ResolvingAuthenticationManager 对象，该对象再赋值给 JwtIssuerAuthenticationManagerResolver 对象的成员变量 authenticationManager。
            那么，实际上是不同的 issuer对象，生成了不同的 JwtAuthenticationProvider对象，最后通过方法引用，生成了不同的 AuthenticationManager 对象，
            其又被用作了 JwtIssuerAuthenticationManagerResolver 的构造器的参数。

        接下来，JwtIssuerAuthenticationManagerResolver.resolve() 获取 AuthenticationManager 对象，实现类类型是 ResolvingAuthenticationManager，
        然后 ResolvingAuthenticationManager.authenticate() 方法认证 authentication 对象：
            首先，调用 ResolvingAuthenticationManager对象的成员变量 issuerAuthenticationManagerResolver
            的 resolve(issuer) 方法，根据不同的 issuer 获取不同的 AuthenticationManager 对象，然后 调用 AuthenticationManager 对象的 authenticate() 方法进行真正的认证。
        小结：所以真正的策略对象是 authenticationManagers 对象中的不同的 AuthenticationManager 对象。
        疑问：authenticationManagers::get，此处的方法引用，内部比较复杂，下次debug一下，看看实际怎么进行的。

    Note: It would be unsafe to simply take any issuer and construct an AuthenticationManager from it.
    The issuer should be one that the code can verify from a trusted source like a list of allowed issuers.
     */

    /**
     * Multitenancy: Resolving the Tenant By Claim: Parsing the Claim Only Once: 1
     * You may have observed that this strategy, while simple, comes with the trade-off that the JWT is parsed once by the AuthenticationManagerResolver and then again by the JwtDecoder later on in the request.
     * This extra parsing can be alleviated by configuring the JwtDecoder directly with a JWTClaimsSetAwareJWSKeySelector from Nimbus:
     * 代码在 TenantJWSKeySelector类中。
     * Next, we can construct a JWTProcessor:
     */
    @Bean
    JWTProcessor jwtProcessor(JWTClaimsSetAwareJWSKeySelector keySelector) {
        ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor();
        jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector);
        return jwtProcessor;
    }

    /**
     * Multitenancy: Resolving the Tenant By Claim: Parsing the Claim Only Once: 4
     * Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our JwtDecoder:
     *
     * @param jwtProcessor
     * @param jwtValidator
     * @return
     */
    @Bean
    JwtDecoder jwtDecoder2(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtValidator) {
        NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor);
        OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>
                (JwtValidators.createDefault(), jwtValidator);
        decoder.setJwtValidator(validator);
        return decoder;
    }

    /**
     * Bearer Tokens: 1 Bearer Token Resolution: 1 Reading the Bearer Token from a Custom Header
     * For example, you may have a need to read the bearer token from a custom header. To achieve this, you can expose a DefaultBearerTokenResolver as a bean, or wire an instance into the DSL, as you can see in the following example:
     * Example 1. Custom Bearer Token Header
     */
    @Bean
    BearerTokenResolver bearerTokenResolver() {
        DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
        bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
        return bearerTokenResolver;
        /*
         Or, in circumstances where a provider is using both a custom header and value, you can use HeaderBearerTokenResolver instead.
         HeaderBearerTokenResolver bearerTokenResolver2 = new HeaderBearerTokenResolver(HttpHeaders.PROXY_AUTHORIZATION);
         return bearerTokenResolver2;
         */
    }

    /**
     * Bearer Tokens: 2 Bearer Token Propagation
     * Now that you’re resource server has validated the token, it might be handy to pass it to downstream services. This is quite simple with ServletBearerExchangeFilterFunction, which you can see in the following example:
     */
    @Bean
    WebClient webClient() {
        return WebClient.builder()
                .filter(new ServletBearerExchangeFilterFunction())
                .build();
    }

    /*
    When the above WebClient is used to perform requests, Spring Security will look up the current Authentication and extract any AbstractOAuth2Token credential. Then, it will propagate that token in the Authorization header.
    For example:
    this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .retrieve()
        .bodyToMono(String.class)
        .block()

    Will invoke the https://other-service.example.com/endpoint, adding the bearer token Authorization header for you.
    In places where you need to override this behavior, it’s a simple matter of supplying the header yourself, like so:
    this.rest.get()
        .uri("https://other-service.example.com/endpoint")
        .headers(headers -> headers.setBearerAuth(overridingToken))
        .retrieve()
        .bodyToMono(String.class)
        .block()
    In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.
    */

    /**
     * Bearer Tokens: 2 Bearer Token Propagation: 1 RestTemplate support
     * There is no RestTemplate equivalent for ServletBearerExchangeFilterFunction at the moment, but you can propagate the request’s bearer token quite simply with your own interceptor:
     *
     * @return
     */
    @Bean
    RestTemplate rest() {
        RestTemplate rest = new RestTemplate();
        rest.getInterceptors().add((request, body, execution) -> {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null) {
                return execution.execute(request, body);
            }

            if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) {
                return execution.execute(request, body);
            }

            AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials();
            request.getHeaders().setBearerAuth(token.getTokenValue());
            return execution.execute(request, body);
        });
        return rest;
    }

    /**
     * Bearer Tokens: 3 Bearer Token Failure
     * A bearer token may be invalid for a number of reasons. For example, the token may no longer be active.
     * In these circumstances, Resource Server throws an InvalidBearerTokenException. Like other exceptions, this results in an OAuth 2.0 Bearer Token error response:
     * error content:
     *     HTTP/1.1 401 Unauthorized
     *     WWW-Authenticate: Bearer error_code="invalid_token", error_description="Unsupported algorithm of none", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
     *
     * Additionally, it is published as an AuthenticationFailureBadCredentialsEvent, which you can listen for in your application like so:
     */
    @Component
    public class FailureEvents {
        @EventListener
        public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) {
            if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) {
                // ... handle
            }
        }
    }
}
